package com.mldong.modules.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mldong.base.CommonPage;
import com.mldong.base.YesNoEnum;
import com.mldong.exception.ServiceException;
import com.mldong.holder.LoginUserHolder;
import com.mldong.modules.sys.dto.MenuPageParam;
import com.mldong.modules.sys.dto.MenuParam;
import com.mldong.modules.sys.dto.SyncRouteParam;
import com.mldong.modules.sys.entity.Menu;
import com.mldong.modules.sys.entity.RoleMenu;
import com.mldong.modules.sys.enums.MenuOpenTypeEnum;
import com.mldong.modules.sys.enums.MenuTypeEnum;
import com.mldong.modules.sys.enums.SysErrEnum;
import com.mldong.modules.sys.enums.UserAdminTypeEnum;
import com.mldong.modules.sys.mapper.MenuMapper;
import com.mldong.modules.sys.mapper.RoleMenuMapper;
import com.mldong.modules.sys.service.MenuService;
import com.mldong.modules.sys.service.RbacService;
import com.mldong.modules.sys.vo.MenuVO;
import com.mldong.modules.sys.vo.RouteMenuVO;
import com.mldong.tree.TreeTool;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单 服务实现类
 * </p>
 *
 * @author mldong
 * @since 2022-10-18
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    private final RoleMenuMapper roleMenuMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(MenuParam param) {
        param.setId(null);
        if(param.getParentId() == null){
            param.setParentId(0L);
        }
        Menu Menu = new Menu();
        BeanUtil.copyProperties(param, Menu);
        if(UserAdminTypeEnum.ADMIN.name().equalsIgnoreCase(param.getCode())) {
            ServiceException.throwBiz(SysErrEnum.MENU_CODE_ERR);
        }
        return super.save(Menu);
    }

    @Override
    public boolean update(MenuParam param) {
        Menu Menu = new Menu();
        BeanUtil.copyProperties(param, Menu);
        if(UserAdminTypeEnum.ADMIN.name().equalsIgnoreCase(param.getCode())) {
            ServiceException.throwBiz(SysErrEnum.MENU_CODE_ERR);
        }
        return super.updateById(Menu);
    }

    @Override
    public CommonPage<MenuVO> page(MenuPageParam param) {
        IPage<MenuVO> page = param.buildMpPage();
        QueryWrapper queryWrapper = param.buildQueryWrapper();
        queryWrapper.eq("t.is_deleted",0);
        List<MenuVO> list = baseMapper.selectCustom(page, queryWrapper);
        page.setRecords(list);
        return CommonPage.toPage(page);
    }

    @Override
    public MenuVO findById(Long id) {
        return baseMapper.findById(id);
    }

    @Override
    public void syncRoute(String appCode,List<SyncRouteParam> paramList) {
        // 同步规则:
        // 1.默认只同步四级数据(可用递归，也可使用四层遍历)
        // 2.以appCode,code为唯一标识进行同步
        // 3.只同步isSync=true或isSync=1的数据
        // 4.同步时，有变化时，要更新，也可全字段更新
        // 5.ext扩展信息存放到variable
        // 6. 当前端删除路由时，后端对应的也要删除掉
        List<String> pidList = CollectionUtil.newArrayList("0");
        // 所有前端路由菜单id集合
        List<Long> menusIds = saveMenu(appCode,0L, pidList,paramList,1);
        LambdaQueryWrapper<Menu> delLambdaQueryWrapper = Wrappers.lambdaQuery();
        delLambdaQueryWrapper.eq(Menu::getAppCode,appCode)
                .notIn(Menu::getId,menusIds)
                .eq(Menu::getIsSync,1);
        // 删除不在前端路由同步的菜单
        List<Long> delIds = baseMapper.selectList(delLambdaQueryWrapper).stream().map(item->{
            return item.getId();
        }).collect(Collectors.toList());
        if(CollectionUtil.isNotEmpty(delIds)) {
            baseMapper.deleteBatchIds(delIds);
            // 删除角色菜单关系
            LambdaQueryWrapper<RoleMenu> delRoleMenuWrapper = Wrappers.lambdaQuery();
            delRoleMenuWrapper.in(RoleMenu::getMenuId,delIds);
            roleMenuMapper.delete(delRoleMenuWrapper);
        }

    }

    @Override
    public List<MenuVO> tree(MenuPageParam param) {
        QueryWrapper queryWrapper = param.buildQueryWrapper();
        queryWrapper.orderByAsc("sort");
        // 方法一：非超级管理员这里只返回用户拥有的菜单
//        if(!LoginUserHolder.get().isSuperAdmin() && ObjectUtil.equals(param.getFilterByUser(),1)){
//            RbacService rbacService = SpringUtil.getBean(RbacService.class);
//            List<String> menuIds = rbacService.getMenuIdsByUserId(LoginUserHolder.getUserId());
//            if(CollectionUtil.isNotEmpty(menuIds)){
//                queryWrapper.in("id",menuIds);
//            } else {
//                return new ArrayList<>();
//            }
//        }
        List<Menu> list = this.list(queryWrapper);
        List<MenuVO> voList = BeanUtil.copyToList(list,MenuVO.class);
        // 方法二：非超级管理员这里只返回所有菜单，非用户拥有的菜单设置disabled=true
        if(!LoginUserHolder.get().isSuperAdmin() && ObjectUtil.equals(param.getFilterByUser(),1)) {
            RbacService rbacService = SpringUtil.getBean(RbacService.class);
            List<String> menuIds = rbacService.getMenuIdsByUserId(LoginUserHolder.getUserId());
            voList.forEach(item -> {
                item.setDisabled(!menuIds.contains(item.getId().toString()));
            });
        }
        List<MenuVO> treeData = TreeTool.listToTree(voList,0L,MenuVO.class);
        return treeData;
    }

    @Override
    public List<RouteMenuVO> userRouteMenu(Long userId) {
        List<Menu> menuList = baseMapper.selectList(
                Wrappers.lambdaQuery(Menu.class)
                        .eq(Menu::getEnabled,YesNoEnum.YES)
                        //.ne(Menu::getCode,"sys:routelist")
                        .in(Menu::getType,CollectionUtil.newArrayList(MenuTypeEnum.DIR,MenuTypeEnum.MENU))
                .orderByAsc(Menu::getSort)
        );
        if(!LoginUserHolder.get().isSuperAdmin()){
            RbacService rbacService = SpringUtil.getBean(RbacService.class);
            List<String> menuIds = rbacService.getMenuIdsByUserId(LoginUserHolder.getUserId());
            menuList = menuList.stream().filter(item->{
                return menuIds.contains(item.getId().toString());
            }).collect(Collectors.toList());
        }
        List<RouteMenuVO> list = new ArrayList<>();

        menuList.forEach(menu -> {
            RouteMenuVO vo = new RouteMenuVO();
            vo.setId(menu.getId());
            vo.setParentId(menu.getParentId());
            vo.setName(menu.getCode());
            vo.setComponent(menu.getComponent());
            vo.setRedirect(null);
            vo.setPath(menu.getPath());
            vo.getMeta().putAll(JSONUtil.toBean(menu.getVariable(), Dict.class));
            vo.getMeta().set("title",menu.getName());
            if(StrUtil.isNotEmpty(menu.getIcon())){
                vo.getMeta().set("icon", menu.getIcon());
            }
            if(MenuOpenTypeEnum.LINK.getCode().equals(menu.getOpenType())){
                vo.getMeta().put("link",menu.getUrl());
            } else if(MenuOpenTypeEnum.IFRAME.getCode().equals(menu.getOpenType())){
                vo.getMeta().put("iframeSrc",menu.getUrl());
            }
            if(ObjectUtil.equals(YesNoEnum.NO.getCode(),menu.getIsShow())){
                vo.getMeta().put("hideInMenu", true);
            }
            if(ObjectUtil.equals(YesNoEnum.YES.getCode(),menu.getIsCache())){
                vo.getMeta().put("keepAlive", true);
            }
            // 如果不同步，则按钮权限码使用数据库数据
            if(ObjectUtil.equals(menu.getIsSync(),YesNoEnum.N)){
                List<Menu> subMenu = baseMapper.selectList(
                        Wrappers.lambdaQuery(Menu.class)
                                .eq(Menu::getParentId,menu.getId())
                                .in(Menu::getType,CollectionUtil.newArrayList(
                                        MenuTypeEnum.BUTTON,MenuTypeEnum.INTERFACE))
                                .orderByAsc(Menu::getSort)
                );
                LinkedHashMap linkedHashMap = new LinkedHashMap();
                subMenu.forEach(item->{
                    linkedHashMap.put(item.getCode(),item.getName());
                });
                vo.getMeta().put("btns",linkedHashMap);
            }
            list.add(vo);
        });
        return TreeTool.listToTree(list, 0L,RouteMenuVO.class);
    }

    /**
     * 根据应用编码和菜单编码查询
     * @param appCode
     * @param code
     * @return
     */
    private Menu findOneByAppCodeAndCode(String appCode,String code) {
        LambdaQueryWrapper<Menu> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(Menu::getAppCode,appCode).eq(Menu::getCode,code);
        return baseMapper.selectOne(lambdaQueryWrapper);
    }

    /**
     * 保存菜单
     * @param appCode
     * @param parentId
     * @param pidList
     * @param list
     * @param maxLevel
     */
    private List<Long> saveMenu(String appCode,Long parentId,List<String> pidList,List<SyncRouteParam> list,int maxLevel) {
        List<Long> menusIds = new ArrayList<>();
        if(CollectionUtil.isEmpty(list) || maxLevel>4) return menusIds;
        list.stream().filter(item->{
            return ObjectUtil.equals(item.getIsSync(), YesNoEnum.Y) && !UserAdminTypeEnum.ADMIN.name().equalsIgnoreCase(item.getCode());
        }).forEach(item->{
            item.setAppCode(appCode);
            // 遍历第一级
            Menu menu = findOneByAppCodeAndCode(item.getAppCode(),item.getCode());
            if(menu==null) {
                // 新增
                menu = new Menu();
                menu.setParentId(parentId);
                BeanUtil.copyProperties(item,menu,"parentId","children");
                menu.setVariable(JSONUtil.toJsonStr(item.getExt()));
                menu.setPids(StrUtil.join(",",pidList));
                baseMapper.insert(menu);
            } else {
                if(ObjectUtil.equals(menu.getIsSync(),YesNoEnum.Y)){
                    // 更新
                    BeanUtil.copyProperties(item,menu,"id","parentId","children");
                    menu.setVariable(JSONUtil.toJsonStr(item.getExt()));
                    menu.setPids(StrUtil.join(",",pidList));
                    baseMapper.updateById(menu);
                }
            }
            List<String> newPidList = new ArrayList<>();
            newPidList.addAll(pidList);
            newPidList.add(menu.getId().toString());
            menusIds.add(menu.getId());
            List<Long> childIds = saveMenu(appCode,menu.getId(),newPidList,item.getChildren(),maxLevel+1);
            menusIds.addAll(childIds);
        });
        return menusIds;
    }
}
